package com.example.servicegateway.authentication.component;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.example.servicegateway.entity.TokenInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;

@Slf4j
public abstract class AuthComponent {

    @Resource
    protected StringRedisTemplate stringRedisTemplate;

    public final Mono<AuthorizationDecision> authentication(TokenInfo tokenInfo, String path, Mono<Authentication> authentication, AuthorizationContext authorizationContext) {
        // 判断JWT中携带的用户角色是否有权限访问
        return authentication.filter(Authentication::isAuthenticated)
                .filter(this::checkUser)
                .flatMapIterable(Authentication::getAuthorities)
                .map(GrantedAuthority::getAuthority)
                .any(authority -> grant(tokenInfo, authority, path))
                .map(AuthorizationDecision::new)
                .defaultIfEmpty(new AuthorizationDecision(false));
    }

    /**
     * 检查用户是否登录过
     */
    private boolean checkUser(Object obj) {
        if (!(obj instanceof JwtAuthenticationToken)) {
            log.warn("不是JwtAuthenticationToken对象");
            return false;
        }

        JwtAuthenticationToken token = (JwtAuthenticationToken) obj;
        Jwt jwt = token.getToken();
        JSONObject jsonObject = new JSONObject(jwt.getClaims());
        TokenInfo tokenInfo = JSON.parseObject(jsonObject.toJSONString(), TokenInfo.class);
        String userName = tokenInfo.getUserName();
        String value = stringRedisTemplate.opsForValue().get(getRedisTokenKey(tokenInfo.getUserId()));

        if (StringUtils.isBlank(value)) {
            log.info("用户{}未登录", userName);
            return false;
        }
        if (!StringUtils.equals(jwt.getTokenValue(), value)) {
            log.warn("用户{} token 无效", userName);
            return false;
        }
        log.info("用户校验成功：{}", userName);
        return true;
    }

    /**
     * 授权校验
     * @param authority 权限
     * @param path 请求url
     * @return true-授权成功
     */
    protected abstract boolean grant(TokenInfo tokenInfo, String authority, String path);

    protected abstract String getRedisTokenKey(String userId);
}
